/**************************************************************************************************
* Title: SumCamera.cpp
* Author: Gael Huber
* Description: Describes a basic camera class
**************************************************************************************************/
#include "SumCamera.h"

/**************************************************************************************************
* Default constructor
**************************************************************************************************/
Camera::Camera(void)
	: fov(0.0f), aspect(0.0f), nearPlane(0.0f), farPlane(0.0f)
{
	MatrixIdentity_16f(&projection);
	MatrixIdentity_16f(&view);
	QuaternionIdentity_16f(&orientation);
}

/**************************************************************************************************
* Constructor defining primary characteristics of viewing frustrum
**************************************************************************************************/
Camera::Camera(float fov, float aspect, float zn, float zf)
	: fov(fov), aspect(aspect), nearPlane(zn), farPlane(zf)
{
	MatrixPerspectiveFovRH_16f(&projection, fov, aspect, nearPlane, farPlane);
	MatrixIdentity_16f(&view);
	QuaternionIdentity_16f(&orientation);
}

/**************************************************************************************************
* Copy constructor
**************************************************************************************************/
Camera::Camera(const Camera& rhs) {
	// Copy view and projection matrix
	projection = rhs.getProjectionMatrix();
	view = rhs.getViewMatrix();

	// Extract aspect ratio, field of view, near clip plane and far clip plane from the projection
	// matrix
	fov = tan(projection._22) * 2.0f;
	aspect = projection._22 / projection._11;
	nearPlane = projection._33 * projection._43;
	farPlane = (projection._43 * nearPlane) / (nearPlane + projection._43);

	// Copy position and orientation
	position = rhs.getPosition();
	orientation = rhs.getOrientation();
}

/**************************************************************************************************
* Destructor
**************************************************************************************************/
Camera::~Camera(void)
{ }

/**************************************************************************************************
* Given an axis and an angle, rotate the camera
**************************************************************************************************/
void Camera::rotate(const Vector3_16f& axis, float angle) {
	// Create a rotation quaternion from the given axis and angle
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &axis, angle);
	
	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Rotate the camera by a quaternion
**************************************************************************************************/
void Camera::rotate(const Quaternion_16f& q) {
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q);
}

/**************************************************************************************************
* Give the camera a yaw about the Y axis
**************************************************************************************************/
void Camera::yaw(float angle) {
	// Construct a quaternion to rotate about Y
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &Vector3_16f(0.0f, 1.0f, 0.0f), angle);

	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Give the camera a yaw around the local Y axis
**************************************************************************************************/
void Camera::yawRelative(float angle) {
	// Construct a quaternion to rotate about y
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &getUp(), angle);

	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Give the camera a pitch about the X axis
**************************************************************************************************/
void Camera::pitch(float angle) {
	// Construct a quaternion to rotate about x
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &Vector3_16f(1.0f, 0.0f, 0.0f), angle);

	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Give the camera a pitch around the local X axis
**************************************************************************************************/
void Camera::pitchRelative(float angle) {
	// Construct a quaternion to rotate about x
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &getRight(), angle);

	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Give the camera a roll about the Z axis
**************************************************************************************************/
void Camera::roll(float angle) {
	// Construct a quaternion to rotate about z
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &Vector3_16f(0.0f, 0.0f, -1.0f), angle);

	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Give the camera a roll around the local Z axis
**************************************************************************************************/
void Camera::rollRelative(float angle) {
	// Construct a quaternion to rotate about x
	Quaternion_16f q1;
	QuaternionRotationAxis_16f(&q1, &getDirection(), angle);

	// Rotate the camera
	Quaternion_16f q2(orientation);
	QuaternionMultiply_16f(&orientation, &q2, &q1);
}

/**************************************************************************************************
* Set the camera's orientation to a given quaternion
**************************************************************************************************/
void Camera::setOrientation(const Quaternion_16f& q) {
	orientation = q;
}

/**************************************************************************************************
* Gets the current orientation
**************************************************************************************************/
const Quaternion_16f& Camera::getOrientation(void) const {
	return orientation;
}

/**************************************************************************************************
* Look at a given location in space
**************************************************************************************************/
void Camera::lookAt(const Vector3_16f& pos) {
	// Construct a lookAt matrix and extract the quaternion
	Matrix_16f lookAtMatrix;
	MatrixLookAtRH_16f(&lookAtMatrix, &position, &pos, &Vector3_16f(0.0f, 1.0f, 0.0f));
	QuaternionRotationMatrix_16f(&orientation, &lookAtMatrix);
}

/**************************************************************************************************
* Look at a given location in space
**************************************************************************************************/
void Camera::lookAt(float x, float y, float z) {
	// Construct a lookAt matrix and extract the quaternion
	Matrix_16f lookAtMatrix;
	MatrixLookAtRH_16f(&lookAtMatrix, &position, &Vector3_16f(x, y, z), &Vector3_16f(0.0f, 1.0f, 0.0f));
	QuaternionRotationMatrix_16f(&orientation, &lookAtMatrix);
}

/**************************************************************************************************
* Get the forward direction the camera is facing
**************************************************************************************************/
Vector3_16f Camera::getDirection(void) const {
	Vector3_16f direction;
	QuaternionMultiply_16f(&direction, &orientation, &Vector3_16f(0.0f, 0.0f, -1.0f));
	return direction;
}

/**************************************************************************************************
* Get the vector pointing right compared to the forward
**************************************************************************************************/
Vector3_16f Camera::getRight(void) const {
	Vector3_16f right;
	QuaternionMultiply_16f(&right, &orientation, &Vector3_16f(1.0f, 0.0f, 0.0f));
	return right;
}

/**************************************************************************************************
* Get the up vector
**************************************************************************************************/
Vector3_16f Camera::getUp(void) const {
	Vector3_16f up;
	QuaternionMultiply_16f(&up, &orientation, &Vector3_16f(0.0f, 1.0f, 0.0f));
	return up;
}

/**************************************************************************************************
* Move the camera by an absolute distance relative to world axes
**************************************************************************************************/
void Camera::move(const Vector3_16f& dist) {
	Vec3Add_16f(&position, &position, &dist);
}

/**************************************************************************************************
* Move the camera by an absolute distance relative to world axes
**************************************************************************************************/
void Camera::move(float x, float y, float z) {
	Vec3Add_16f(&position, &position, &Vector3_16f(x, y, z));
}

/**************************************************************************************************
* Move the camera by an absolute distance relative to the camera orientation
**************************************************************************************************/
void Camera::moveRelative(const Vector3_16f& dist) {
	// Multiply the orientation by the translation vector to get the relative movement vector
	Vector3_16f trans;
	QuaternionMultiply_16f(&trans, &orientation, &dist);

	// Add the translation
	Vec3Add_16f(&position, &position, &trans);
}

/**************************************************************************************************
* Move the camera by an absolute distance relative to the camera orientation
**************************************************************************************************/
void Camera::moveRelative(float x, float y, float z) {
	// Multiply the orientation by the translation vector to get the relative movement vector
	Vector3_16f trans;
	QuaternionMultiply_16f(&trans, &orientation, &Vector3_16f(x, y, z));

	// Add the translation
	Vec3Add_16f(&position, &position, &trans);
}

/**************************************************************************************************
* Get the camera position
**************************************************************************************************/
const Vector3_16f Camera::getPosition(void) const {
	return position;
}

/**************************************************************************************************
* Set the absolute position
**************************************************************************************************/
void Camera::setPosition(const Vector3_16f& pos) {
	position = pos;
}

/**************************************************************************************************
* Set the absolute position
**************************************************************************************************/
void Camera::setPosition(float x, float y, float z) {
	position.x = x;
	position.y = y;
	position.z = z;
}

/**************************************************************************************************
* Get the projection matrix
**************************************************************************************************/
const Matrix_16f& Camera::getProjectionMatrix(void) const {
	return projection;
}

/**************************************************************************************************
* Get the view matrix
**************************************************************************************************/
const Matrix_16f& Camera::getViewMatrix(void) const {
	return view;
}

/**************************************************************************************************
* Get the combination of the view and projection matrix
**************************************************************************************************/
Matrix_16f Camera::getViewProjMatrix(void) const {
	Matrix_16f viewproj;
	MatrixMultiply_16f(&viewproj, &view, &projection);
	return viewproj;
}

/**************************************************************************************************
* Update the view matrix based on current camera data
**************************************************************************************************/
void Camera::updateView(void) {
	// Create a matrix for rotation and translation
	Matrix_16f translation, rotation;
	MatrixTranslation_16f(&translation, position.x, position.y, position.z);
	MatrixRotationQuaternion_16f(&rotation, &orientation);

	// Create the new transformation matrix
	MatrixMultiply_16f(&view, &translation, &rotation);
}